//
//  JavaBridge.m
//  iCodeMac
//
//  Created by Andrew Barry on 25/07/09.
//  Copyright 2009 Barry Computer Services. All rights reserved.
//

#import "JavaBridge.h"
#import "jvm.h"

static jsvalue javaWrappedConstructor(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	return JSVAL_VOID;
}

static jsvalue javaNew(jscontext *cx, jsframe *outer, jsvalue me, jsvalue *args, int argc) {
	if (argc < 1)
		return JSVAL_VOID;

	NSString *className = [jsasstrRetain(cx, args[0]) autorelease];
	JNIEnv *env = [jvm instance]->env;

	(*env)->PushLocalFrame(env, 10);
	
	jclass cls = (*env)->FindClass(env, [className UTF8String]);
	if (!cls)
		goto abort;
	
	jmethodID constructor = (*env)->GetMethodID(env, cls, "<init>", "()V");
	if (!constructor)
		goto abort;
	
	jobject obj = (*env)->NewObject(env, cls, constructor);

	if (!obj)
		goto abort;
	
	// turn the local reference into a global one
	obj = (*env)->NewGlobalRef(env, obj);
	
	jsvalue wrapcls = jsget(cx, cx->slots[19], 0, "WrappedObject");
	jsvalue wrap = jsnew(cx, wrapcls, nil, 0);
	jsset(cx, wrap, 0, "_jobj", 1 | (int) obj);

	(*env)->PopLocalFrame(env, nil);
	return wrap;
	
abort:
	(*env)->PopLocalFrame(env, nil);
	return JSVAL_VOID;
}

static jsvalue javaCustomGet(jscontext *cx, jsvalue me, int h, const char *name) {
	jsvalue v = jsgetslot(cx, me, 0, "_jobj");
	if (v) {
		jobject obj = (jobject) (v & ~1);
		JNIEnv *env = [jvm instance]->env;

		jclass bridgeClass = (*env)->FindClass(env, "bcs/icode/bridge/JavaBridge");
		jmethodID bridgeGet = (*env)->GetStaticMethodID(env, bridgeClass, "get",
											   "(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;");

		jstring jsname = (*env)->NewStringUTF(env, name);

		jobject result = (*env)->CallStaticObjectMethod(env, bridgeClass, bridgeGet, obj, jsname);

		if ((*env)->ExceptionCheck(env))
			return JSVAL_VOID;

		if (result) {
			jsvalue wrapcls = jsget(cx, cx->slots[19], 0, "WrappedObject");
			jsvalue wrap = jsnew(cx, wrapcls, nil, 0);
			jsset(cx, wrap, 0, "_jobj", 1 | (int) result);
			return wrap;
		} else {
			return JSVAL_NULL;
		}
	}
	
	return JSVAL_VOID;
}

jsvalue javaCustomInvoke(jscontext *cx, jsvalue func, jsvalue thisArg, jsvalue *params, int paramCount) {
	jsvalue funcv = jsgetslot(cx, func, 0, "_jobj");
	jsvalue thisv = jsgetslot(cx, thisArg, 0, "_jobj");
	if (funcv && thisv) {
		jobject funcobj = (jobject) (funcv & ~1);
		jobject thisobj = (jobject) (thisv & ~1);
		JNIEnv *env = [jvm instance]->env;
		
		jclass bridgeClass = (*env)->FindClass(env, "bcs/icode/bridge/JavaBridge");
		jmethodID bridgeInvoke = (*env)->GetMethodID(env, bridgeClass, "invoke",
														"(Ljava/lang/Object;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");

		jobject invokeParams = (*env)->NewObjectArray(env, paramCount, (*env)->FindClass(env, "java/lang/Object"), nil);
		
		for (int idx = 0; idx < paramCount; idx++) {
			jsvalue v = params[idx];
			if (v == JSVAL_VOID || v == JSVAL_NULL) {
			} else if (JSVAL_IS_STRING(v)) {
				NSString *str = jsasstrRetain(cx, v);
				(*env)->SetObjectArrayElement(env, invokeParams, idx, (*env)->NewStringUTF(env, [str UTF8String]));
				[str release];
			} else {
				unimplemented();
			}
		}
		
		
		jobject result = (*env)->CallStaticObjectMethod(env, bridgeClass, bridgeInvoke, thisobj, funcobj, invokeParams);
		
		if ((*env)->ExceptionCheck(env))
			return JSVAL_VOID;
		
		if (result) {
			jsvalue wrapcls = jsget(cx, cx->slots[19], 0, "WrappedObject");
			jsvalue wrap = jsnew(cx, wrapcls, nil, 0);
			jsset(cx, wrap, 0, "_jobj", 1 | (int) result);
			return wrap;
		} else {
			return JSVAL_NULL;
		}
	}
	
	return JSVAL_VOID;
}

void javaDestructor(jscontext *cx, jsvalue obj) {
	jsvalue v = jsgetslot(cx, obj, 0, "_jobj");
	if (v) {
		jobject obj = (jobject) (v & ~1);
		JNIEnv *env = [jvm instance]->env;
		(*env)->DeleteGlobalRef(env, obj);
	}
}

void registerJavaBridge(jscontext *cx) {
	jsvalue javaWrapClass;
	cx->slots[19] = jsnewobject(cx);
	jsset(cx, cx->slots[19], 0, "newObject", bindFunction(cx, javaNew));

	jsset(cx, cx->slots[19], 0, "WrappedObject", javaWrapClass = bindFunction(cx, javaWrappedConstructor));

	((jsfunction *) javaWrapClass)->customGet = javaCustomGet;
	((jsfunction *) javaWrapClass)->customInvoke = javaCustomInvoke;
	((jsfunction *) javaWrapClass)->destructor = javaDestructor;
}
